#pragma once

#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstdlib>
#include <ctime>

#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>

#include "Log.hpp"
#include "Util.hpp"
#include "dbManager.hpp" // 数据库管理头文件
#include "db_redis.hpp"  // Redis管理头文件

#define HTTP_REQ_HEADER_SEP ": "
#define WEB_ROOT "./webroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define BLANK_LINE "\r\n"

const std::string page_403 = "./webroot/403.html";
const std::string page_404 = "./webroot/404.html";

class HttpRequest
{
public:
    std::string reqLine;
    std::vector<std::string> reqHeader;
    std::string blank;
    std::string reqBody;

    std::string method;
    std::string uri;
    std::string version;

    // uri = uriPath + uriParam
    std::string uriPath;
    std::string uriParam;
    bool cgi; // 存在uriParam或POST正文或reqSrc被认定为exe 则为CGI请求
    std::string uriPathSuffix;

    std::unordered_map<std::string, std::string> reqHeaderMap;
    int content_length; // 请求正文大小

    int resSrcFd;   // http请求的资源文件描述符
    int reqSrcSize; // http请求的资源大小
public:
    HttpRequest()
        : content_length(0),
          cgi(false)
    {
    }

    ~HttpRequest() {}
};

class HttpResponse
{
public:
    std::string resLine;
    std::vector<std::string> resHeader;
    std::string blank;
    std::string resBody;

    std::string version;
    int status_code; // 默认状态码为200 之后遇到错误修改 遇到成功不变
    std::string code_desc;

    // int content_type;
    int content_length;

    std::string httpResMsg; // 除了响应体的响应报文

    int cgi_retCode;        // 处理CGI请求函数的返回值
    std::string cgi_outMsg; // CGI程序的输出

public:
    HttpResponse()
        : blank(LINE_END),
          status_code(OK),
          content_length(0),
          cgi_retCode(OK)

    {
    }
    ~HttpResponse() {}
};

// 读取请求，分析请求，构建响应 IO通信
class EndPoint
{
private:
    HttpRequest http_req;
    HttpResponse http_res;

    int _sockFd;

public:
    // 是否存在请求体
    bool IsHttpReqBody()
    {
        // 是post请求且存在Content-Length字段
        if (http_req.method == "POST")
        {
            auto &reqHeaderMap = http_req.reqHeaderMap;
            auto iter = reqHeaderMap.find("Content-Length");
            if (iter != reqHeaderMap.end())
            {
                // LOG(INFO, "Post Method, Content-Length: %s", iter->second.c_str());
                http_req.content_length = atoi(iter->second.c_str());
                return true;
            }
        }
        return false;
    }

    // 读取并解析请求报文
    bool Recv_Parse_HttpReq()
    {
        Util::printLog("查看获取到的请求begin");
        // 1.接收并解析请求行
        auto &reqLine = http_req.reqLine;
        if (Util::ReadLine(_sockFd, reqLine) > 0)
        {
            std::cout << reqLine; // print请求行

            reqLine.resize(reqLine.size() - 1); // 去掉换行符
            // 1.1 解析请求行
            std::stringstream ss(reqLine);
            ss >> http_req.method >> http_req.uri >> http_req.version;
            // 1.2 请求方法和http版本统一转换为大写
            std::transform(http_req.method.begin(), http_req.method.end(), http_req.method.begin(), ::toupper);
            std::transform(http_req.version.begin(), http_req.version.end(), http_req.version.begin(), ::toupper);

            // 1.3.1 分割GET请求的uri: /a/b/c/test.html?x=10&y=20
            // uri -> uriPath + uriParam
            if (http_req.method == "GET")
            {
                size_t pos = http_req.uri.find('?');
                if (pos != std::string::npos)
                {
                    Util::CutString(http_req.uri, http_req.uriPath, http_req.uriParam, "?");
                    http_req.cgi = true;
                    // std::cout << "uriPath: " << http_req.uriPath << "uriParam: " << http_req.uriParam << std::endl;
                }
                else
                {
                    http_req.uriPath = http_req.uri;
                    // std::cout << "uriPath: " << http_req.uriPath << std::endl;
                }
            }
            // 1.3.2 POST请求的uriPath就是uri
            else if (http_req.method == "POST")
            {
                http_req.uriPath = http_req.uri;
                http_req.cgi = true;
            }
            else
            {
                // Other Method
            }

            // 1.4 构建http请求的真实uriPath /a/index.html -> ./webroot/a/index.html
            http_req.uriPath = WEB_ROOT + http_req.uriPath;
            if (http_req.uriPath.back() == '/')
            {
                // 请求路径以/结尾("/" or "/a/b/c/") 默认请求该路径下的HOME_PAGE
                http_req.uriPath += HOME_PAGE;
            }
            // std::cout << "uriPath: " << http_req.uriPath << " uriParam: " << http_req.uriParam << std::endl;

            // 1.5 请求的资源是一个目录如 /a/b/c -> /a/b/c/index.html
            struct stat st;                               // int stat(const char *pathname, struct stat *statbuf); ok: 0; error: -1
            if (stat(http_req.uriPath.c_str(), &st) == 0) // 资源存在; 资源不存在的情况在构建响应时处理
            {
                if (S_ISDIR(st.st_mode)) // 资源是一个目录
                {
                    http_req.uriPath += "/";
                    http_req.uriPath += HOME_PAGE;
                    stat(http_req.uriPath.c_str(), &st); // 重新获取stat
                }

                // 拥有者 所属组 其他人 任意一方对请求的资源被拥有可执行权限 则该资源被认定为一个可执行程序
                if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
                {
                    http_req.cgi = true;
                }

                http_req.reqSrcSize = st.st_size;
            }

            // 1.6 获取请求资源的后缀
            std::size_t pos = http_req.uriPath.rfind(".");
            if (pos == std::string::npos)
            {
                http_req.uriPathSuffix = ".html";
            }
            else
            {
                http_req.uriPathSuffix = http_req.uriPath.substr(pos);
            }
            // 输出解析后的请求行
            std::cout << http_req.method << " " << http_req.uriPath << " " << http_req.uriParam << " " << http_req.version << "[handled]" << std::endl;
        }
        // 读取http请求行失败
        else
        {
            LOG(WARNING, "RecvHttpReqLine error");
            return false;
        }

        // 2.接收并解析请求头
        std::string reqHeadPerLine;
        std::string key;
        std::string value;
        while (true)
        {
            reqHeadPerLine.clear();
            if (Util::ReadLine(_sockFd, reqHeadPerLine) > 0)
            {
                if (reqHeadPerLine == "\n")
                {
                    http_req.blank = reqHeadPerLine;
                    std::cout << http_req.blank; // print空行
                    break;
                }
                else
                {
                    std::cout << reqHeadPerLine; // print请求头

                    reqHeadPerLine.resize(reqHeadPerLine.size() - 1);
                    http_req.reqHeader.push_back(reqHeadPerLine);
                    // 解析请求头
                    if (Util::CutString(reqHeadPerLine, key, value, HTTP_REQ_HEADER_SEP))
                    {
                        http_req.reqHeaderMap.insert(std::make_pair(key, value));
                        // std::cout << key << ": " << value << std::endl; // debug: 测试是否正确解析请求头
                    }
                }
            }
            else
            {
                LOG(WARNING, "RecvHttpReqHeader error");
                return false;
            }
        }

        // 3.接收并解析请求体
        if (IsHttpReqBody())
        {
            int content_length = http_req.content_length;

            char ch = 0;
            while (content_length)
            {
                ssize_t s = recv(_sockFd, &ch, 1, 0);
                if (s > 0)
                {
                    http_req.reqBody.push_back(ch);
                    content_length--;
                }
                else
                {
                    LOG(WARNING, "RecvHttpReqBody error");
                    return false;
                }
            }
            // 输出请求体
            std::cout << http_req.reqBody << std::endl;
        }
        Util::printLog("查看获取到的请求end");

        return true;
    }

    int ProcessCGI()
    {
        // 父进程向output写(关闭output[0]) 从input读(关闭input[1])
        // 子进程从output读(关闭output[1]) 向input写(关闭input[0])
        int output[2];
        int input[2];

        if (pipe(input) < 0)
        {
            LOG(ERROR, "pipe input error");
            return http_res.status_code = SERVER_ERROR;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "pipe output error");
            close(input[0]);
            close(input[1]);
            return http_res.status_code = SERVER_ERROR;
        }

        // 创建环境变量 请求方法 请求参数[GET] 请求体长度[POST]
        setenv("METHOD", http_req.method.c_str(), 1); // 0 = 不替换  1 = 替换

        // POST方法将请求正文通过管道output[1]传给子进程
        if (http_req.method == "POST")
        {
            const char *start = http_req.reqBody.c_str();
            int total = 0, write_size = 0;
            while (total < http_req.content_length && (write_size = write(output[1], start + total, http_req.reqBody.size() - total)) > 0)
            {
                total += write_size;
            }

            // std::string contentLength_ENV = "CONTENT_LENGTH=" + std::to_string(http_req.content_length);
            // char *env_var = new char[contentLength_ENV.size() + 1]; // putenv需要env_var存在直到程序结束
            // strcpy(env_var, contentLength_ENV.c_str());
            // putenv(env_var);

            // 改为setenv
            setenv("CONTENT_LENGTH", std::to_string(http_req.content_length).c_str(), 1);
        }
        else if (http_req.method == "GET")
        {
            // std::string uriParam_ENV = "REQ_PARAM=" + http_req.uriParam;
            // char *env_var = new char[uriParam_ENV.size() + 1]; // putenv需要env_var存在直到程序结束
            // strcpy(env_var, uriParam_ENV.c_str());
            // putenv(env_var);

            // 改为setenv
            setenv("REQ_PARAM", http_req.uriParam.c_str(), 1);
        }
        else
        {
            // Other Method
        }

        // debug: 查看环境变量是否设置成功
        std::cout << "=============================查看环境变量start=====================" << std::endl;
        char *env = getenv("METHOD");
        std::cout << "METHOD: " << ((env == NULL) ? "NULL" : env) << std::endl;
        env = getenv("REQ_PARAM");
        std::cout << "REQ_PARAM: " << ((env == NULL) ? "NULL" : env) << std::endl;
        env = getenv("CONTENT_LENGTH");
        std::cout << "CONTENT_LENGTH: " << ((env == NULL) ? "NULL" : env) << std::endl;
        env = getenv("SESSION_TOKEN");
        std::cout << "SESSION_TOKEN: " << ((env == NULL) ? "NULL" : env) << std::endl;
        std::cout << "=============================查看环境变量end=====================" << std::endl;

        pid_t pid = fork();
        if (pid < 0)
        {
            LOG(ERROR, "fork error");
            close(output[0]);
            close(output[1]);
            close(input[0]);
            close(input[1]);
            return http_res.status_code = SERVER_ERROR;
        }

        if (pid == 0)
        {
            // child
            close(output[1]);
            close(input[0]);

            dup2(output[0], 0); // 将子进程的标准输入重定向到父进程的output[0]
            dup2(input[1], 1);  // 将子进程的标准输出重定向到父进程的input[1]

            close(output[0]);
            close(input[1]);

            // 子进程继承父进程的环境变量 程序替换不会替换环境变量
            execl(http_req.uriPath.c_str(), http_req.uriPath.c_str(), nullptr);

            // 如果 execl 失败 退出子进程
            LOG(ERROR, "execl failed");
            _exit(EXIT_FAILURE);
        }
        else if (pid > 0)
        {
            // parent
            close(output[0]);
            close(input[1]);

            // 获取子进程通过input[0]传来的CGI响应报文
            char ch = 0;
            while (read(input[0], &ch, 1) > 0)
            {
                http_res.cgi_outMsg.push_back(ch);
            }

            close(output[1]);
            close(input[0]);

            int status = 0;
            pid_t ret;
            while ((ret = waitpid(pid, &status, WNOHANG)) == 0)
            {
                sleep(1); // 子进程未退出 继续循环 等待一段时间再继续检查
            }
            if (ret == -1)
            {
                LOG(ERROR, "waitpid error: %s", strerror(errno)); // 打印具体的错误信息
                return http_res.status_code = SERVER_ERROR;
            }
            // 子进程异常终止
            if (!WIFEXITED(status))
            {
                LOG(ERROR, "child process terminated abnormally");
                return http_res.status_code = SERVER_ERROR;
            }

            // 子进程正常退出但退出码不为0
            if (WEXITSTATUS(status) != 0)
            {
                LOG(ERROR, "child process terminated normally but exit code is not 0");
                return http_res.status_code = BAD_REQUEST;
            }

            // 子进程正常退出且退出码为0
            LOG(INFO, "child process terminated normally");
            http_res.status_code = OK;
        }
        return http_res.status_code;
    }

    int ProcessAuth()
    {
        LOG(INFO, "ProcessAuth");
        auto it = http_req.reqHeaderMap.find("Cookie");
        std::string session_token;

        // Cooki字段不存在 返回提示页面
        if (it == http_req.reqHeaderMap.end())
            return http_res.status_code = 403;

        // Cooki字段存在 但是没有session_token字段 返回提示页面
        std::string cookie = it->second;
        auto pos = cookie.find("session_token");
        if (pos == std::string::npos)
            return http_res.status_code = 403;
        // Cookie中有session_token字段 但是值为空 返回提示页面
        // 获取session_token值
        size_t start = pos + strlen("session_token=");
        size_t end = cookie.find(";", start);
        session_token = cookie.substr(start, end - start);
        if (session_token.empty())
            return http_res.status_code = 403;

        // 1. 查询Redis
        RedisManager &redis = RedisManager::GetInstance();
        std::string username = redis.GetUserByToken(session_token);

        // Redis命中
        if (!username.empty())
        {
            setenv("SESSION_TOKEN", session_token.c_str(), 1);
            LOG(INFO, "Redis hit: %s", username.c_str());
            return http_res.status_code = OK;
        }

        // 2. 查询数据库
        DBManager &db = DBManager::GetInstance();
        int retCode = db.CheckSessionToken(session_token);
        // Cookie存在且含有非空session_token 但是session_token无效 返回提示页面
        if (retCode == SESSION_TOKEN_INVALID)
            return http_res.status_code = 403;

        // 代码走到此处 说明session_token有效
        int expire = 3600 - (rand() % 300); // 3600-3899秒随机
        redis.InsertToken(session_token, username, expire);
        LOG(INFO, "DB hit and update redis: %s", username.c_str());
        setenv("SESSION_TOKEN", session_token.c_str(), 1); // int setenv(const char *name, const char *value, int overwrite);
        return http_res.status_code = OK;
    }

    // 构建OK响应的响应头部字段
    void BuildOKResHeader()
    {
        std::string line;
        line = "Content-Type: ";
        line += Util::reqPathSuffixToDesc(http_req.uriPathSuffix);
        line += LINE_END;
        http_res.resHeader.push_back(line);

        line = "Content-Length: ";
        line += std::to_string(http_req.reqSrcSize);
        line += LINE_END;
        http_res.resHeader.push_back(line);
    }

    // 构建403响应的响应头部字段
    void Build403ResHeader()
    {
        http_req.cgi = false;
        http_req.resSrcFd = open(page_403.c_str(), O_RDONLY);
        if (http_req.resSrcFd > 0)
        {
            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_res.resHeader.push_back(line);

            struct stat st;
            stat(page_403.c_str(), &st);
            http_req.reqSrcSize = st.st_size;
            line = "Content-Length: ";
            line += std::to_string(http_req.reqSrcSize);
            line += LINE_END;
            http_res.resHeader.push_back(line);
        }
        else
        {
            // 403页面就是为了处理错误情况 如果此时403页面打开错误 服务器直接退出
            LOG(ERROR, "open %s error!", page_403.c_str());
            exit(5);
        }
    }

    // 构建404响应的响应头部字段
    void Build404ResHeader()
    {
        http_req.cgi = false;
        http_req.resSrcFd = open(page_404.c_str(), O_RDONLY);
        if (http_req.resSrcFd > 0)
        {
            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_res.resHeader.push_back(line);

            struct stat st;
            stat(page_404.c_str(), &st);
            http_req.reqSrcSize = st.st_size;
            line = "Content-Length: ";
            line += std::to_string(http_req.reqSrcSize);
            line += LINE_END;
            http_res.resHeader.push_back(line);
        }
        else
        {
            // 404页面就是为了处理错误情况 如果此时404页面打开错误 服务器直接退出
            LOG(ERROR, "open %s error!", page_404.c_str());
            exit(5);
        }
    }

    // 判断是否在访问需要权限的资源
    bool NeedAuth()
    {
        return http_req.uriPath == "./webroot/blog.html" || http_req.uriPath == "./webroot/cgi/article" || http_req.uriPath == "./webroot/cgi/comment" || http_req.uriPath == "./webroot/cgi/search";
    }

public:
    EndPoint(int _sock)
        : _sockFd(_sock)
    {
    }

    void BuildHttpRes()
    {
        // http请求分类：
        // 1. 普通请求 单纯拉取资源的请求(GET方法不带参)
        // 2. cgi请求  GET带参 POST(默认带参) 请求exe

        // http响应分类：
        // 1. 对于普通请求 构建响应头 返回其请求的资源
        // 2.1 cgi请求且processCGI成功 http响应即为cgi程序输出到管道的内容
        // 2.2 虽为cgi请求但processCGI失败 http响应为404页面

        // 非法请求
        if (http_req.method != "GET" && http_req.method != "POST")
        {
            LOG(WARNING, "method %s is not right", http_req.method.c_str());
            http_res.status_code = BAD_REQUEST;
            goto END;
        }

        // 资源不存在
        struct stat st; // int stat(const char *pathname, struct stat *statbuf); ok: 0; error: -1
        if (stat(http_req.uriPath.c_str(), &st) != 0)
        {
            LOG(WARNING, "%s Not Found", http_req.uriPath.c_str());
            http_res.status_code = NOT_FOUND;
            goto END;
        }
        http_req.reqSrcSize = st.st_size;

        // 打开资源失败
        http_req.resSrcFd = open(http_req.uriPath.c_str(), O_RDONLY);
        if (http_req.resSrcFd < 0)
        {
            LOG(WARNING, "%s open failed!", http_req.uriPath.c_str());
            http_res.status_code = SERVER_ERROR;
            goto END;
        }
#ifndef TEST
        // 访问受限资源 调用ProcessAuth
        if (NeedAuth())
        {
            if (ProcessAuth() != OK)
                goto END;
        }
#endif
        // cgi请求 调用processCGI
        if (http_req.cgi)
        {
            // http_res.cgi_retCode = ProcessCGI();
            if (ProcessCGI() != OK)
                goto END;
        }
    END:
        // 1. 构建状态行 [不管资源是否存在 状态行构建方式不变]
        http_res.version = HTTP_VERSION;
        std::string code = std::to_string(http_res.status_code);
        http_res.code_desc = Util::statusCodeToDesc(http_res.status_code);
        http_res.resLine = http_res.version + " " + code + " " + http_res.code_desc + LINE_END;

        // 2. 根据状态码构建不同的响应报头
        switch (http_res.status_code)
        {
        case OK:
            BuildOKResHeader();
            break;
        case NOT_FOUND:
            Build404ResHeader();
            break;
        case FORBIDDEN:
            Build403ResHeader();
            break;
        case BAD_REQUEST:
            Build404ResHeader();
            break;
        case SERVER_ERROR:
            Build404ResHeader();
            break;
        default:
            break;
        }

        // 3. 响应体不用构建
        // 普通请求的响应体即是http_req.reqSrcFd指向的资源 发送时直接sendfile即可
        // CGI请求的响应报文由CGI程序构建

        Util::printLog("查看构建的响应begin");

        if (http_req.cgi && http_res.cgi_retCode == OK && http_res.status_code == OK)
        {
            std::cout << http_res.cgi_outMsg << std::endl;
        }
        // 普通请求的响应正文直接从文件拷贝到套接字 此处只输出响应行和响应头和空行
        else
        {
            http_res.httpResMsg += http_res.resLine;
            for (auto iter : http_res.resHeader)
            {
                http_res.httpResMsg += iter;
            }
            http_res.httpResMsg += http_res.blank;
            std::cout << http_res.httpResMsg;
        }

        Util::printLog("查看构建的响应end");
    }

    void SendHttpRes()
    {
        // 如果处理cgi请求的函数全部成功执行(pipe/fork/waitpid/cgi程序正常退出)
        // 并且本次请求没有其他因素影响状态码 针对cgi请求的http响应报文采纳子进程的输出

        // 除此之外的情况按照普通请求的http响应报文输出
        if (http_req.cgi && http_res.cgi_retCode == OK && http_res.status_code == OK)
        {
            std::string cgiMsg = http_res.cgi_outMsg;
            size_t cgiMsgSize = cgiMsg.size();

            size_t size = 0;
            size_t total = 0;
            const char *start = cgiMsg.c_str();
            while (total < cgiMsgSize && (size = send(_sockFd, start + total, cgiMsgSize - total, 0)) > 0)
            {
                total += size;
            }
        }
        else
        {
            // 发送状态行 响应报头 空行
            send(_sockFd, http_res.httpResMsg.c_str(), http_res.httpResMsg.size(), 0);

            // 发送响应正文
            // ssize_t sendfile(int __out_fd, int __in_fd, off_t *__offset, size_t __count)
            sendfile(_sockFd, http_req.resSrcFd, nullptr, http_req.reqSrcSize);
            close(http_req.resSrcFd);
        }
    }

    ~EndPoint()
    {
        close(_sockFd);
    }
};

class CallBack
{
public:
    CallBack()
    {
    }

    void operator()(int _sockFd)
    {
        HandlerRequest(_sockFd);
    }

    void HandlerRequest(int _sockFd)
    {
        EndPoint *ep = new EndPoint(_sockFd);

        // 读取http请求发生错误 直接结束本次请求
        if (!ep->Recv_Parse_HttpReq())
        {
            LOG(WARNING, "Recv_Parse_HttpReq Error, Stop Build And Send");
            return;
        }

        ep->BuildHttpRes();
        ep->SendHttpRes();

        delete ep;
    }

    ~CallBack()
    {
    }
};
